package com.dbms.transaction;

import com.dbms.common.RID;
import com.dbms.common.Schema;
import com.dbms.common.Tuple;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * 事务日志记录类（LogRecord）
 * 核心作用：封装单次数据库操作的日志信息，支持序列化到磁盘（持久化）和反序列化（恢复时读取）
 * 覆盖的操作类型：事务控制（BEGIN/COMMIT/ROLLBACK）、数据修改（INSERT/DELETE/UPDATE）
 * 日志记录是事务ACID特性的关键保障：崩溃恢复时通过日志回滚未提交事务、重做已提交事务
 */
public class LogRecord {

    /**
     * 日志类型枚举：定义所有支持的日志操作类型
     * - INVALID：默认错误类型（未初始化或解析失败）
     * - INSERT：插入元组操作日志
     * - DELETE：删除元组操作日志
     * - UPDATE：更新元组操作日志（需记录旧值用于回滚）
     * - BEGIN：事务开始日志
     * - COMMIT：事务提交日志
     * - ROLLBACK：事务回滚日志
     */
    public enum LogType {
        INVALID, // 默认或错误类型
        INSERT,
        DELETE,
        UPDATE,
        BEGIN,
        COMMIT,
        ROLLBACK
    }

    // --- 日志记录头部固定格式（共12字节）---
    // 1. size (4字节)：整个日志记录的总长度（包含头部）
    // 2. logType (4字节)：日志类型（LogType的ordinal值）
    // 3. txnId (4字节)：所属事务ID
    public static final int HEADER_SIZE = 12;

    // --- 日志记录核心字段 ---
    private final LogType logType;       // 日志类型（如INSERT、COMMIT）
    private final int transactionId;     // 所属事务ID（关联日志与事务）
    private final String tableName;      // 操作的表名（数据修改类日志必需）
    private final RID rid;               // 元组ID（页号+槽位，INSERT/UPDATE日志必需）
    private final Tuple oldTuple;        // 旧元组数据（DELETE/UPDATE日志必需，用于回滚）

    // --- 重载构造函数：对应不同类型的日志记录 ---
    /**
     * 事务控制类日志构造函数（BEGIN/COMMIT/ROLLBACK）
     * 无需表名、RID或元组数据，仅需日志类型和事务ID
     * @param logType 日志类型（BEGIN/COMMIT/ROLLBACK）
     * @param transactionId 所属事务ID
     */
    public LogRecord(LogType logType, int transactionId) { // For BEGIN, COMMIT, ROLLBACK
        this.logType = logType;
        this.transactionId = transactionId;
        this.tableName = ""; // 空字符串占位
        this.rid = null;
        this.oldTuple = null;
    }

    /**
     * INSERT操作日志构造函数
     * 需要表名和元组RID（插入的元组位置），无需旧元组（插入无历史值）
     * @param transactionId 所属事务ID
     * @param tableName 操作的表名
     * @param rid 插入元组的ID（页号+槽位）
     */
    public LogRecord(int transactionId, String tableName, RID rid) { // For INSERT
        this.logType = LogType.INSERT;
        this.transactionId = transactionId;
        this.tableName = tableName;
        this.rid = rid;
        this.oldTuple = null;
    }

    /**
     * DELETE操作日志构造函数
     * 需要表名和被删除的元组数据（用于回滚时重新插入），无需RID（元组数据已包含关键信息）
     * @param transactionId 所属事务ID
     * @param tableName 操作的表名
     * @param deletedTuple 被删除的元组完整数据
     */
    public LogRecord(int transactionId, String tableName, Tuple deletedTuple) { // For DELETE
        this.logType = LogType.DELETE;
        this.transactionId = transactionId;
        this.tableName = tableName;
        this.rid = null;
        this.oldTuple = deletedTuple;
    }

    /**
     * UPDATE操作日志构造函数
     * 需要表名、元组RID（更新的元组位置）和更新前的旧元组（用于回滚时恢复）
     * @param transactionId 所属事务ID
     * @param tableName 操作的表名
     * @param rid 更新元组的ID（页号+槽位）
     * @param oldTuple 更新前的元组完整数据
     */
    public LogRecord(int transactionId, String tableName, RID rid, Tuple oldTuple) { // For UPDATE
        this.logType = LogType.UPDATE;
        this.transactionId = transactionId;
        this.tableName = tableName;
        this.rid = rid;
        this.oldTuple = oldTuple;
    }

    // --- Getter方法：提供字段访问权限（只读，日志记录不可修改）---
    public LogType getLogType() { return logType; }
    public int getTransactionId() { return transactionId; }
    public String getTableName() { return tableName; }
    public RID getRid() { return rid; }
    public Tuple getOldTuple() { return oldTuple; }

    /**
     * 序列化：将日志记录转换为字节数组，用于写入日志文件
     * 完整字节格式（按顺序）：
     * 1. 头部（HEADER_SIZE=12字节）：size(4B) + logType(4B) + txnId(4B)
     * 2. 表名信息：tableNameLength(4B) + tableName(变长，UTF-8编码)
     * 3. RID信息：hasRid(1B，0=无/1=有) + [pageId(4B) + slotNum(4B)]（仅hasRid=1时存在）
     * 4. 元组信息：hasTuple(1B，0=无/1=有) + [tupleSize(4B) + tupleData(变长)]（仅hasTuple=1时存在）
     * @return 序列化后的字节数组（包含完整日志记录）
     */
    public byte[] serialize() {
        // 初始化4096字节缓冲区（足够容纳单条日志，避免频繁扩容）
        ByteBuffer buffer = ByteBuffer.allocate(4096);

        // 1. 头部占位：先写0，最后回填总长度
        buffer.putInt(0);
        // 2. 写入日志类型（ordinal值，4字节）
        buffer.putInt(logType.ordinal());
        // 3. 写入事务ID（4字节）
        buffer.putInt(transactionId);

        // 4. 写入表名：先写长度，再写字节数组
        byte[] tableNameBytes = tableName.getBytes(StandardCharsets.UTF_8);
        buffer.putInt(tableNameBytes.length); // 表名长度（4字节）
        buffer.put(tableNameBytes);           // 表名字节（变长）

        // 5. 写入RID：先标记是否存在，再写RID内容（若存在）
        if (rid != null) {
            buffer.put((byte) 1); // hasRid=1（存在RID）
            buffer.putInt(rid.getPageId());  // 元组所在页号（4字节）
            buffer.putInt(rid.getSlotNum()); // 元组在页内的槽位（4字节）
        } else {
            buffer.put((byte) 0); // hasRid=0（无RID）
        }

        // 6. 写入元组：先标记是否存在，再写元组内容（若存在）
        if (oldTuple != null) {
            buffer.put((byte) 1); // hasTuple=1（存在元组）
            byte[] tupleData = oldTuple.getData(); // 元组序列化后的字节数组
            buffer.putInt(tupleData.length);       // 元组长度（4字节）
            buffer.put(tupleData);                 // 元组数据（变长）
        } else {
            buffer.put((byte) 0); // hasTuple=0（无元组）
        }

        // 7. 回填总长度：当前缓冲区位置即总字节数
        int totalSize = buffer.position();
        buffer.putInt(0, totalSize);

        // 8. 截取有效字节数组（去除缓冲区多余空间）
        byte[] result = new byte[totalSize];
        buffer.flip(); // 切换为读模式
        buffer.get(result); // 复制有效数据到结果数组
        return result;
    }

    /**
     * 反序列化：从字节数组恢复为LogRecord对象，用于崩溃恢复时读取日志
     * 需严格按照serialize()定义的格式解析，依赖schemaMap获取表结构以正确解析元组
     * @param data 待解析的日志字节数组（完整单条日志记录）
     * @param schemaMap 表名→Schema映射表（解析Tuple时需表结构信息）
     * @return 反序列化后的LogRecord对象（解析失败返回null）
     * @throws IllegalArgumentException 日志长度不匹配或格式错误时抛出
     */
    public static LogRecord deserialize(byte[] data, Map<String, Schema> schemaMap) {
        // 包装字节数组为ByteBuffer，便于按类型读取
        ByteBuffer buffer = ByteBuffer.wrap(data);

        // 1. 读取总长度并校验（确保字节数组完整）
        int totalSize = buffer.getInt();
        if (data.length != totalSize) {
            throw new IllegalArgumentException("Log record size mismatch: expected " + totalSize + ", got " + data.length);
        }

        // 2. 读取日志类型（通过ordinal值转换为LogType）
        LogType logType = LogType.values()[buffer.getInt()];
        // 3. 读取事务ID
        int txnId = buffer.getInt();

        // 4. 读取表名：先读长度，再读字节数组
        int tableNameLength = buffer.getInt();
        byte[] tableNameBytes = new byte[tableNameLength];
        buffer.get(tableNameBytes);
        String tableName = new String(tableNameBytes, StandardCharsets.UTF_8);

        // 5. 读取RID（若存在）
        RID rid = null;
        if (buffer.get() == 1) { // hasRid=1，存在RID
            int pageId = buffer.getInt();
            int slotNum = buffer.getInt();
            rid = new RID(pageId, slotNum);
        }

        // 6. 读取元组（若存在）
        Tuple tuple = null;
        if (buffer.get() == 1) { // hasTuple=1，存在元组
            // 从schemaMap获取表结构（解析元组必需）
            Schema schema = schemaMap.get(tableName);
            // 兼容系统表（如_users_）可能未在schemaMap中的情况，暂不抛出异常
            if (schema != null) {
                int tupleSize = buffer.getInt(); // 元组长度
                byte[] tupleData = new byte[tupleSize]; // 元组字节数组
                buffer.get(tupleData);
                // 用表结构解析元组字节数组
                tuple = new Tuple(tupleData, schema);
            }
        }

        // 7. 根据日志类型调用对应构造函数，返回LogRecord对象
        switch (logType) {
            case BEGIN:
            case COMMIT:
            case ROLLBACK:
                return new LogRecord(logType, txnId);
            case INSERT:
                return new LogRecord(txnId, tableName, rid);
            case DELETE:
                return new LogRecord(txnId, tableName, tuple);
            case UPDATE:
                return new LogRecord(txnId, tableName, rid, tuple);
            default:
                return null; // 无效日志类型，返回null
        }
    }
}